<?php
/**
 * Auflistung und Weiterverarbeitung von Html-Tags
 *
 * @author		Joachim Ruf
 * @copyright	Copyright (c) 2003-2010 Loresoft Software (http://www.loresoft.de)
 * @license		http://yapary.loresoft.de/lizenzbedingungen     New BSD License
 * @TODO
 * */


Class Yapary_WWW_Html_Tag
{
    private $arrSingleTag = array('img', 'br', 'input', 'hr');


    /**
	 * Listet alle in $content befindlichen Treffer des Tags $tag auf
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $tag       : Zu ermittelnder Tag
	 * @return Array|NULL		: Array(0=>globPosStart, 1=>globPosEnd; 2=>outerTag, 3=>openTag, 4=>innerTag, 5=>endTag)
	 * */
    public function getTagList ($content, $tag) {
        $arrResult = array();
        $offset = 0;

        while ($arr = $this->getTag($content, $tag, $offset)) {
            $offset = $arr[1];
            $arrResult[] = $arr;
        }

        return $arrResult;
    }

    /**
	 * Listet alle in $content befindlichen Treffer des Tags $tag auf
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $param     : Zu ermittelnder Parameter {id|class|style|...}
	 * @param string $paramVal	: Zu ermittelnder Parameterwert
	 * @return Array|NULL		: Array(0=>globPosStart, 1=>globPosEnd; 2=>outerTag, 3=>openTag, 4=>innerTag, 5=>endTag)
	 * */
    public function getTagListByParam ($content, $param, $paramVal) {
        $arrResult = array();
        $offset = 0;

        while ($arr = $this->getTagByParam($content, $param, $paramVal, $offset)) {
            $offset = $arr[1];
            $arrResult[] = $arr;
        }

        return $arrResult;
    }

    /**
	 * Listet alle in $content befindlichen Treffer des Tags $tag auf
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $tag       : Name des Tags
	 * @param string $param     : Zu ermittelnder Parameter {id|class|style|...}
	 * @param string $paramVal	: Zu ermittelnder Parameterwert
	 * @return Array|NULL		: Array(0=>globPosStart, 1=>globPosEnd; 2=>outerTag, 3=>openTag, 4=>innerTag, 5=>endTag)
	 * */
    public function getTagListByNameParam ($content, $tag, $param, $paramVal) {
        $arrResult = array();
        $offset = 0;

        while ($arr = $this->getTagByNameParam($content, $tag, $param, $paramVal, $offset)) {
            $offset = $arr[1];
            $arrResult[] = $arr;
        }

        return $arrResult;
    }

    /**
	 * Listet den ersten in $content befindlichen Treffer des Tags $tag auf
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $tag       : Zu ermittelnder Tag
	 * @param int    $offset	: Offset, ab welchem die Suche beginnen soll
	 * @return Array|NULL		: Array(0=>globPosStart, 1=>globPosEnd; 2=>outerTag, 3=>openTag, 4=>innerTag, 5=>endTag)
	 * */
    public function getTag ($content, $tag, $offset = 0) {
        if (in_array($tag, $this->arrSingleTag)) {
            return $this->getSingleTag($content, $tag, $offset);
        } else {
            return $this->getMultipleTag($content, $tag, $offset);
        }
    }

    /**
	 * Listet den ersten in $content befindlichen Treffer des Tags $tag auf
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $param     : Zu ermittelnder Parameter {id|class|style|...}
	 * @param string $paramVal	: Zu ermittelnder Parameterwert
	 * @param int    $offset	: Offset, ab welchem die Suche beginnen soll
	 * @return Array|NULL		: Array(0=>globPosStart, 1=>globPosEnd; 2=>outerTag, 3=>openTag, 4=>innerTag, 5=>endTag)
	 * */
    public function getTagByParam ($content, $param, $paramVal, $offset = 0) {
        /**
         * 1. Ermittle alle Tags, welche das angegebene Attribut beinhalten
         * 2. Baue Liste mit tag und Startindex (offset) auf
         * 3. durchlaufe die Liste und deligiere getTag()
         * */
        $tag       = NULL;

        // ok // <([a-z]{1,})\s*([a-z]{1,}=("|')(.*?)("|')\s*)*(id="listtable"|id='listtable')\s*(.*?)>
        // vereinfacht //  "/<([a-z]{1,})\s*([a-z]{2,}=(\".*?\"|\'.*?\')\s*)*($param=\"$paramVal\"|$param='$paramVal')\s*(\/?.*?)>/ims";
        $pattern   = "/<([a-z]{1,})\s*([a-z]{2,}=(\".*?\"|\'.*?\')\s*)*($param=\"$paramVal\"|$param='$paramVal')(\s*[a-z]{2,}=(\".*?\"|\'.*?\')\s*)*(\s*)\/?>/im";
        $arrResult = array();
        $arrTmp    = array();

        if (!preg_match($pattern, $content, $arrTmp, PREG_OFFSET_CAPTURE, $offset)) {
            return NULL;
        }
//        echo "<pre>";
//        echo print_r($arrTmp, true);
//        echo "</pre>";

        $tag    = $arrTmp[1][0];
        $offset = $arrTmp[0][1];

        return $this->getTag($content, $tag, $offset);
    }

    /**
	 * Listet den ersten in $content befindlichen Treffer des Tags $tag mit dem Parameter $param=$paramVal auf
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $tag       : Name des Tags
	 * @param string $param     : Zu ermittelnder Parameter {id|class|style|...}
	 * @param string $paramVal	: Zu ermittelnder Parameterwert
	 * @param int    $offset	: Offset, ab welchem die Suche beginnen soll
	 * @return Array|NULL		: Array(0=>globPosStart, 1=>globPosEnd; 2=>outerTag, 3=>openTag, 4=>innerTag, 5=>endTag)
	 * */
    public function getTagByNameParam ($content, $tag, $param, $paramVal, $offset = 0) {
        /**
         * 1. Ermittle alle Tags, welche das angegebene Attribut beinhalten
         * 2. Baue Liste mit tag und Startindex (offset) auf
         * 3. durchlaufe die Liste und deligiere getTag()
         * */
        $pattern   = "/<($tag)\s*([a-z]{2,}=(\".*?\"|\'.*?\')\s*)*($param=\"$paramVal\"|$param='$paramVal')(\s*[a-z]{2,}=(\".*?\"|\'.*?\')\s*)*(\s*)\/?>/im";
        $arrResult = array();
        $arrTmp    = array();

        if (!preg_match($pattern, $content, $arrTmp, PREG_OFFSET_CAPTURE, $offset)) {
            return NULL;
        }
//        echo "<pre>";
//        echo print_r($arrTmp, true);
//        echo "</pre>";

        //$tag    = $arrTmp[1][0];
        $offset = $arrTmp[0][1];

        return $this->getTag($content, $tag, $offset);
    }

    /**
	 * Listet den ersten in $content befindlichen Treffer eines SingleTags $tag auf
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $tag       : Zu ermittelnder Tag
	 * @param int    $offset	: Offset, ab welchem die Suche beginnen soll
	 * @return Array|NULL		: Array(0=>posStart, 1=>posEnd; 2=>content)
	 * */
    private function getSingleTag ($content, $tag, $offset = 0) {
        $pattern = "/<$tag(\s*([a-z]{2,}=(\".*?\"|\'.*?\')(\s|))*|)(\/?\s*)>/im";
        $arrResult = array();

        if (!preg_match($pattern, $content, $arrResult, PREG_OFFSET_CAPTURE, $offset)) {
            return NULL;
        }
//        echo "<pre style=\"border: solid 1px gray;\">";
//        echo print_r($arrResult, true);
//        echo "</pre>";

        return array($arrResult[0][1],
                     $arrResult[5][1]+1,
                     $arrResult[5][0]);
    }

    /**
	 * Listet den ersten in $content befindlichen Treffer eines MultipleTags $tag auf
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $tag       : Zu ermittelnder Tag
	 * @param int    $offset	: Offset, ab welchem die Suche beginnen soll
	 * @return Array|NULL		: Array(0=>globPosStart, 1=>globPosEnd; 2=>outerTag, 3=>openTag, 4=>innerTag, 5=>endTag)
	 * */
    private function getMultipleTag ($content, $tag, $offset = 0) {
        /*
         * 1. Baue Liste mit <tag> und </tag> auf
         * 2. gibt es von <tag> oder </tag> 0 Elemente, dann breche ab
         * 3. gibt es nur 1 <tag> und 1 </tag> dann passt es
         * 4. gibt es mehr <tag> als </tag>, dann gehe bis zum letzten auffindbaren </tag>
         * 5. gibt es mehr </tag> als <tag>, dann vernachlaessige letzte(s) </tag>
         * 6. gibt es mehrere <tag> und </tag>, dann:
         *    * zaehle wie viele <tag>-Elemente nacheinander folgen (N), ohne dass ein </tag> dazwischen liegt
         *    * N-tes Element aus </tag> gehoert dann zu 1-tem Element von <tag>!
         * */

        // ok // <([a-z]{1,})\s*([a-z]{1,}=("|')(.*?)("|'))?(\s*\/?)>
        $patternHead = "/<$tag(\s*([a-z]{2,}=(\".*?\"|\'.*?\')(\s|))*|)>/im";
        $patternTail = "/(<\/$tag(\s*|)>)/im";
        $arrResultHead = array();
        $arrResultTail = array();
        $countHead     = NULL;
        $countTail     = NULL;

        // Nr.1+2
        if (!preg_match_all($patternHead, $content, $arrResultHead, PREG_OFFSET_CAPTURE, $offset)) {
            return NULL;
        }
        if (!preg_match_all($patternTail, $content, $arrResultTail, PREG_OFFSET_CAPTURE, $offset)) {
            return NULL;
        }
        $countHead = count($arrResultHead[0]);
        $countTail = count($arrResultTail[0]);

//        echo "<pre>";
//        echo print_r($arrResultHead, true);
//        echo print_r($arrResultTail, true);
//        echo "</pre>";

        // Nr.6 + 4+5
        $countStack = 0;
        $head       = array_shift($arrResultHead[0]);
        $tail       = array_shift($arrResultTail[2]);
        $innerStart = array_shift($arrResultHead[4]);
        $endTag     = array_shift($arrResultTail[0]);
        $main = $head;
        do {
            if (count($arrResultHead[0]) == 0 && count($arrResultTail[2]) == 0) { // Nr.3
                break;
            }
            if ($head[1] > $tail[1] | $head[1] == 'endofdata') {
                if (--$countStack == 0) {
                    break;
                }
                $tail     = array_shift($arrResultTail[2]);
                $endTag = array_shift($arrResultTail[0]);
            } else {
                $countStack++;
                if (count($arrResultHead[0]) == 0) {
                    $head = array(0=>'', 1=>'endofdata');
                } else {
                    $head = array_shift($arrResultHead[0]);
                }
            }
//            if (count($arrResultHead[0]) == 0) { // muss nach > und < Abfrage folgen
//                if (--$countStack == 0) {
//                    break;
//                }
//                $tail     = array_shift($arrResultTail[2]);
//                $endTag = array_shift($arrResultTail[0]);
//            }
            //echo "stack($countStack) ";
        } while ($countStack > 0 && count($arrResultTail[2]) > 0);

        //echo substr($content, $main[1], $tail[1]+1-$main[1]);
        $outerTag = substr($content, $main[1], $tail[1]+1-$main[1]);
        $innerTag = substr($outerTag, strlen($main[0]), strlen($outerTag)-strlen($main[0])-strlen($endTag[0]));
        return array($main[1],
                     $tail[1]+1,
                     $outerTag,
                     $main[0],
                     $innerTag,
                     $endTag[0]
                     );
    }

    /**
	 * Gibt alle sich in $content befindlichen Attribute aus
	 * @return Array|NULL
	 * */
    public function getAttributeList ($content) {
        $arrTmp      = array();
        $arrResult   = array();
        $pattern     = "/([a-z]{1,})=(\".*?\"|\'.*?\')/im";

        if (!preg_match_all($pattern, $content, $arrTmp, PREG_OFFSET_CAPTURE)) {
            return NULL;
        }

        for ($i=0; $i<count($arrTmp[0]); $i++) {
            $key = $arrTmp[1][$i][0];
            $val = $arrTmp[2][$i][0];
            $val = substr($val, 1, strlen($val)-2);
            $arrResult[$key] = $val;
        }

        return $arrResult;
    }

    /**
     * Entfernt alle Funde von <tag></tag> bzw. <tag />
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $tag       : Zu ermittelnder Tag
	 * @return content
     * */
    public function removeTags ($content, $tag) {
        while ($arr = $this->getTag($content, $tag)) {
            $front = substr($content, 0, $arr[0]);
            $end   = substr($content, $arr[1]+1, strlen($content)-$arr[1]-1);
            $content = $front.$end;
        }

        return $content;
    }

    /**
     * Entfernt den ersten Fund von <tag></tag> bzw. <tag />
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $tag       : Zu ermittelnder Tag
	 * @param int    $index		: index des zu entfernenden Elements
	 * @return content
     * */
    public function removeTag ($content, $tag, $index = 0) {
        $arr = array();

        if (($arr = $this->getTagList($content, $tag)) == NULL) {
            return $content;
        }
        if (!isset($arr[$index])) {
            return $content;
        }

        $arr = $arr[$index];
        $front = substr($content, 0, $arr[0]);
        $end   = substr($content, $arr[1]+1, strlen($content)-$arr[1]-1);
        return $front.$end;
    }

    /**
     * Entfernt alle Funde von <tag></tag> bzw. <tag />
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $param     : Zu entfernender Parameter {id|class|style|...}
	 * @param string $paramVal	: Zu entfernender Parameterwert
	 * @return content
     * */
    public function removeTagsByParam ($content, $param, $paramVal) {
        while ($arr = $this->getTagByParam($content, $param, $paramVal)) {
//        echo "<pre>";
//        echo print_r($arr, true);
//        echo "</pre>";

            $front = substr($content, 0, $arr[0]);
            $end   = substr($content, $arr[1], strlen($content)-$arr[1]);
            $content = $front.$end;
        }

        return $content;
    }

    /**
     * Entfernt den ersten Fund von <tag></tag> bzw. <tag />
	 * @param string $content	: Content, welcher gecrawlt werden soll
	 * @param string $param     : Zu entfernender Parameter {id|class|style|...}
	 * @param string $paramVal	: Zu entfernender Parameterwert
	 * @param int    $index		: index des zu entfernenden Elements
	 * @return content
     * */
    public function removeTagByParam ($content, $param, $paramVal, $index = 0) {
        $arr = array();

        if (($arr = $this->getTagListByParam($content, $param, $paramVal)) == NULL) {
            return $content;
        }
        if (!isset($arr[$index])) {
            return $content;
        }

        $arr = $arr[$index];
        $front = substr($content, 0, $arr[0]);
        $end   = substr($content, $arr[1], strlen($content)-$arr[1]);
        return $front.$end;
    }


}




?>